package cn.jja8.networkTransmission;

import cn.jja8.networkTransmission.tool.ByteDataTransmission;
import cn.jja8.networkTransmission.tool.ByteSegmentation;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据客户框架, 提供在指定频道传输数据的接口
 * */
public class DataClient {

    Map<String, GitPassageway> getPassagewayMap = new HashMap<>();
    /**
     * 打开一个通道，如果这个通道已经打开了就返回旧的。使用通道通信，可以节省大量写代码的时间。
     * */
    public GitPassageway 打开get通道(String 通道名称){
        return 打开get通道(通道名称,30000);
    }
    /**
     * 打开一个通道，如果这个通道已经打开了就返回旧的。使用通道通信，可以节省大量写代码的时间。
     * @param get请求超时时间 毫秒  get请求超过指定时间没被回复将被作废。
     * */
    public GitPassageway 打开get通道(String 通道名称, long get请求超时时间){
        GitPassageway 通道 = getPassagewayMap.get(通道名称);
        if (通道==null){
            通道 = new GitPassageway(this,通道名称,get请求超时时间);
            getPassagewayMap.put(通道名称,通道);
        }
        return 通道;
    }
    Map<String, PutPassageway> putPassagewayMap = new HashMap<>();
    /**
     * 打开一个通道，如果这个通道已经打开了就返回旧的。使用通道通信，可以节省大量写代码的时间。
     * */
    public PutPassageway 打开put通道(String v通道名称){
        PutPassageway 通道 = putPassagewayMap.get(v通道名称);
        if (通道==null){
            通道 = new PutPassageway(this,v通道名称);
            putPassagewayMap.put(v通道名称,通道);
        }
        return 通道;
    }



    /**
     * 一个事件处理接口
     * */
    public interface DataEvents {
        /**
         * 当收到数据时
         * */
        void 收到数据(byte[] 数据,DataClient 连接);
    }
    /**
     * 一个事件处理接口
     * */
    public interface ConnectionBroken {
        /**
         * 当收到数据时
         * */
        void 连接断开(DataClient 连接);
    }
    /**
     * 注册连接断开执行器列表
     * */
    public void 注册连接断开执行器(ConnectionBroken 连接断开执行器){
        b连接断开执行器列表.add(连接断开执行器);
    }
    List<ConnectionBroken> b连接断开执行器列表 = new ArrayList<>();
    Socket 连接;
    InputStream 输入流;
    OutputStream 输出流;
    String 服务器地址;
    int 服务器端口;
    /**
     * 用于服务端构造
     * */
    DataClient(Socket 连接) throws IOException {
        this.连接 = 连接;
        输入流 = 连接.getInputStream();
        输出流 = 连接.getOutputStream();
        /*
        * 未来将在这里发送公钥，和接收密码，下方代码未来将删除
        * */
        byte[] b = "networkTransmission.jja8.cn".getBytes(StandardCharsets.UTF_8);
        for (byte b1 : b) {
            if (b1!= 输入流.read()){
                连接.close();
                System.out.println("收到一个异常的连接请求！");
                return;
            }
        }
    }
    /**
     * 连接到一个服务器
     * */
    public DataClient(String 服务器地址, int 服务器端口) throws IOException {
        this.服务器地址 = 服务器地址;
        this.服务器端口 = 服务器端口;
        连接服务器();
    }
    private final Map<String,DataEvents> b消息接收器map = new HashMap<>();
    /***
     * 注册消息消息接收器，（不推荐，建议使用通道）
     */
    public void 注册数据接收器(String 数据名称,DataEvents 数据接收器){
        if(数据接收器==null){
            b消息接收器map.remove(数据名称);
        }
        b消息接收器map.put(数据名称,数据接收器);
    }
    /**
     * 获取ip地址
     * */
    public InetAddress 获取ip地址(){
        return 连接.getInetAddress();
    }
    /**
     * 获取端口
     * */
    public int 获取端口(){
        return 连接.getPort();
    }
    /**
     * 发送数据，（不推荐，建议使用通道）
     * */
    public synchronized void 发送数据(String 数据名称,byte[] 数据) throws IOException {
        /*
         * 未来在这里加密
         * */
        ByteDataTransmission.发送(输出流,ByteSegmentation.合成(数据名称.getBytes(StandardCharsets.UTF_8),数据));
    }
    boolean 已经开始接收数据 = false;

    /**
     * 当连接被设置好之后调用此方块，开始接收数据
     * @return null 已经开始接收数据了，没有new新的线程
     */
    public synchronized Thread 开始接收数据(){
        if (已经开始接收数据){
            return null;
        }
        Thread thread = new Thread("数据接收线程"){
            @Override
            public void run() {
                接收数据线程();
            }
        };
        thread.start();
        已经开始接收数据 = true;
        return thread;
    }
    /**
     * 使用自己的线程接收数据,会使用调用此方法的线程接收数据。
     * */
    public synchronized void 使用自己的线程接收数据(){
        if (已经开始接收数据){
            throw new RuntimeException("此连接已经开始接收数据！");
        }
        接收数据线程();
    }



    private void 接收数据线程() {
        try {
            while (true){
                接收数据();
            }
        } catch (SocketException e){
            System.out.println("客户端断开了连接");
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (连接关闭){
            return;
        }
        try {
            输入流.close();
            输出流.close();
        } catch (IOException ignored) { }
        已经开始接收数据 = false;
        for (ConnectionBroken da : b连接断开执行器列表) {
            try {
                if (da!=null){
                    da.连接断开(DataClient.this);
                }
            }catch (Throwable e){
                e.printStackTrace();
                System.out.println("接收器在处理断开连接数据时发生了无法处理的异常");
            }
        }
    }

    /**
     * 当连接断开时，可以调用此方法尝试重新连接
     * 此方法会在构造方法里自动调用
     * */
    public void 连接服务器() throws IOException {
        连接 = new Socket(服务器地址, 服务器端口);
        输入流 = 连接.getInputStream();
        输出流 = 连接.getOutputStream();
        //发送验证数据 “networkTransmission.jja8.cn”
        //未来将删除下方代码
        输出流.write("networkTransmission.jja8.cn".getBytes(StandardCharsets.UTF_8));
        //未来将删除上方代码
        /*
         * 未来在这里获取公钥,将加密过的密码和验证文本发给服务器
         * 接收格式（密钥）
         * 发送格式 “networkTransmission.jja8.cn +（密码，验证文本）”
         * */
    }
    private void 接收数据() throws IOException {
        byte[] b数据 = ByteDataTransmission.接收(输入流);
        /*
        * 未来在这里解密
        * */
        ByteSegmentation.Byte2 byte2 = ByteSegmentation.拆分(b数据);
        String b数据名称 = new String(byte2.b1,StandardCharsets.UTF_8);
        DataEvents b消息接收器 = b消息接收器map.get(b数据名称);
        if (b消息接收器!=null){
            try {
                b消息接收器.收到数据(byte2.b2, this);
            }catch (Throwable e){
                e.printStackTrace();
                System.out.println("接收器在处理数据时发生了无法处理的异常");
            }
        }else {
            System.out.println("貌似收到一条垃圾消息，指定消息接收器“"+b数据名称+"”但是并没有注册此消息接收器！");
        }
    }
    boolean 连接关闭 = false;
    public void 关闭连接(){
        try {
            连接关闭 = true;
            连接.close();
        } catch (IOException e) { }
    }
}
